package opa

import (
	"fmt"
	"github.com/nuttech/bell"
	"github.com/open-policy-agent/opa/rego"
	"github.com/stretchr/testify/assert"
	"gitverse.ru/synapse/kubelatte/pkg/api/v1alpha1"
	"gitverse.ru/synapse/kubelatte/pkg/logs"
	"go.uber.org/zap"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/yaml"
	"sync"
	"testing"
	"time"
)

const (
	SyncAddNewObjectEvent = "SyncAddNewObjectEvent"
)

func TestOpaManager_CheckRule(t *testing.T) {
	logs.Logger = zap.NewNop()
	rego := OpaManager{}
	rego.Start(false)
	rego.ProcessRule(bell.Message{
		Event:     "Test",
		Timestamp: time.Now(),
		Value: v1alpha1.Template{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "test",
				Namespace: "test",
			},
			Spec: v1alpha1.TemplateSpec{
				Kind:       "Rego",
				ApiVersion: "v1",
				Data:       "violation[{\"msg\": msg}] {\n\tinput.review.object.kind == \"Service\"\n\tmsg := sprintf(\"Create binding '%v' denied. Role disallowed.\", [\"aaa\"])\n}",
				Type:       "rego",
			},
		},
	})
	time.Sleep(500 * time.Millisecond) // goroutine in AddNewObject should be completed

	var object map[string]interface{}
	var data = "{\n        \"uid\": \"d5017de4-505b-4d62-a870-395eee4cc0af\",\n        \"kind\": {\n            \"group\": \"rbac.authorization.k8s.io\",\n            \"version\": \"v1\",\n            \"kind\": \"CustomResourceDefinition\"\n        },\n        \"resource\": {\n            \"group\": \"rbac.authorization.k8s.io\",\n            \"version\": \"v1\",\n            \"resource\": \"rolebindings\"\n        },\n        \"requestKind\": {\n            \"group\": \"rbac.authorization.k8s.io\",\n            \"version\": \"v1\",\n            \"kind\": \"RoleBinding\"\n        },\n        \"requestResource\": {\n            \"group\": \"rbac.authorization.k8s.io\",\n            \"version\": \"v1\",\n            \"resource\": \"rolebindings\"\n        },\n        \"name\": \"kubelatte-user-rb-sandbox2\",\n        \"namespace\": \"kubelatte-sandbox\",\n        \"operation\": \"CREATE\",\n        \"userInfo\": {\n            \"username\": \"system:serviceaccount:kubelatte:kozlov\",\n            \"groups\": [\n                \"system:masters\",\n                \"system:authenticated\"\n            ]\n        },\n        \"object\": {\n            \"kind\": \"RoleBinding\",\n            \"apiVersion\": \"rbac.authorization.k8s.io/v1\",\n            \"metadata\": {\n                \"name\": \"kubelatte-user-rb-sandbox2\",\n                \"namespace\": \"kubelatte-sandbox\",\n                \"uid\": \"04fd2adf-0823-45f5-86b7-08c7aa01ea49\",\n                \"creationTimestamp\": \"2023-07-25T11:44:16Z\",\n                \"labels\": {\n                    \"kblt.creation.resource\": \"true\"\n                },\n                \"annotations\": {\n                    \"kubectl.kubernetes.io/last-applied-configuration\": \"{\\\"apiVersion\\\":\\\"rbac.authorization.k8s.io/v1\\\",\\\"kind\\\":\\\"RoleBinding\\\",\\\"metadata\\\":{\\\"annotations\\\":{},\\\"labels\\\":{\\\"kblt.creation.resource\\\":\\\"true\\\"},\\\"name\\\":\\\"kubelatte-user-rb-sandbox2\\\",\\\"namespace\\\":\\\"kubelatte-sandbox\\\"},\\\"roleRef\\\":{\\\"apiGroup\\\":\\\"rbac.authorization.k8s.io\\\",\\\"kind\\\":\\\"ClusterRole\\\",\\\"name\\\":\\\"synapse-kubelatte-admin-cr\\\"},\\\"subjects\\\":[{\\\"kind\\\":\\\"ServiceAccount\\\",\\\"name\\\":\\\"kblt-user\\\",\\\"namespace\\\":\\\"kubelatte\\\"}]}\\\\n\"\n                },\n                \"managedFields\": [\n                    {\n                        \"manager\": \"kubectl-client-side-apply\",\n                        \"operation\": \"Update\",\n                        \"apiVersion\": \"rbac.authorization.k8s.io/v1\",\n                        \"time\": \"2023-07-25T11:44:16Z\",\n                        \"fieldsType\": \"FieldsV1\",\n                        \"fieldsV1\": {\n                            \"f:metadata\": {\n                                \"f:annotations\": {\n                                    \".\": {},\n                                    \"f:kubectl.kubernetes.io/last-applied-configuration\": {}\n                                },\n                                \"f:labels\": {\n                                    \".\": {},\n                                    \"f:kblt.creation.resource\": {}\n                                }\n                            },\n                            \"f:roleRef\": {},\n                            \"f:subjects\": {}\n                        }\n                    }\n                ]\n            },\n            \"subjects\": [\n                {\n                    \"kind\": \"ServiceAccount\",\n                    \"name\": \"kblt-user\",\n                    \"namespace\": \"kubelatte\"\n                },\n                {\n                    \"kind\": \"ServiceAccount\",\n                    \"name\": \"kbl3-user\",\n                    \"namespace\": \"kubelatte\"\n                }\n            ],\n            \"roleRef\": {\n                \"apiGroup\": \"rbac.authorization.k8s.io\",\n                \"kind\": \"ClusterRole\",\n                \"name\": \"synapse-kubelatte-admin-cr\"\n            }\n        },\n        \"oldObject\": null,\n        \"dryRun\": false,\n        \"options\": {\n            \"kind\": \"CreateOptions\",\n            \"apiVersion\": \"meta.k8s.io/v1\",\n            \"fieldManager\": \"kubectl-client-side-apply\"\n        }\n    }"
	yaml.Unmarshal([]byte(data), &object)
	result, _ := rego.CheckRule("test/test", map[string]interface{}{}, object)
	assert.Equal(t, true, result)

	data = "{\n        \"uid\": \"d5017de4-505b-4d62-a870-395eee4cc0af\",\n        \"kind\": {\n            \"group\": \"rbac.authorization.k8s.io\",\n            \"version\": \"v1\",\n            \"kind\": \"CustomResourceDefinition\"\n        },\n        \"resource\": {\n            \"group\": \"rbac.authorization.k8s.io\",\n            \"version\": \"v1\",\n            \"resource\": \"rolebindings\"\n        },\n        \"requestKind\": {\n            \"group\": \"rbac.authorization.k8s.io\",\n            \"version\": \"v1\",\n            \"kind\": \"RoleBinding\"\n        },\n        \"requestResource\": {\n            \"group\": \"rbac.authorization.k8s.io\",\n            \"version\": \"v1\",\n            \"resource\": \"rolebindings\"\n        },\n        \"name\": \"kubelatte-user-rb-sandbox2\",\n        \"namespace\": \"kubelatte-sandbox\",\n        \"operation\": \"CREATE\",\n        \"userInfo\": {\n            \"username\": \"system:serviceaccount:kubelatte:kozlov\",\n            \"groups\": [\n                \"system:masters\",\n                \"system:authenticated\"\n            ]\n        },\n        \"object\": {\n            \"kind\": \"Service\",\n            \"apiVersion\": \"rbac.authorization.k8s.io/v1\",\n            \"metadata\": {\n                \"name\": \"kubelatte-user-rb-sandbox2\",\n                \"namespace\": \"kubelatte-sandbox\",\n                \"uid\": \"04fd2adf-0823-45f5-86b7-08c7aa01ea49\",\n                \"creationTimestamp\": \"2023-07-25T11:44:16Z\",\n                \"labels\": {\n                    \"kblt.creation.resource\": \"true\"\n                },\n                \"annotations\": {\n                    \"kubectl.kubernetes.io/last-applied-configuration\": \"{\\\"apiVersion\\\":\\\"rbac.authorization.k8s.io/v1\\\",\\\"kind\\\":\\\"RoleBinding\\\",\\\"metadata\\\":{\\\"annotations\\\":{},\\\"labels\\\":{\\\"kblt.creation.resource\\\":\\\"true\\\"},\\\"name\\\":\\\"kubelatte-user-rb-sandbox2\\\",\\\"namespace\\\":\\\"kubelatte-sandbox\\\"},\\\"roleRef\\\":{\\\"apiGroup\\\":\\\"rbac.authorization.k8s.io\\\",\\\"kind\\\":\\\"ClusterRole\\\",\\\"name\\\":\\\"synapse-kubelatte-admin-cr\\\"},\\\"subjects\\\":[{\\\"kind\\\":\\\"ServiceAccount\\\",\\\"name\\\":\\\"kblt-user\\\",\\\"namespace\\\":\\\"kubelatte\\\"}]}\\\\n\"\n                },\n                \"managedFields\": [\n                    {\n                        \"manager\": \"kubectl-client-side-apply\",\n                        \"operation\": \"Update\",\n                        \"apiVersion\": \"rbac.authorization.k8s.io/v1\",\n                        \"time\": \"2023-07-25T11:44:16Z\",\n                        \"fieldsType\": \"FieldsV1\",\n                        \"fieldsV1\": {\n                            \"f:metadata\": {\n                                \"f:annotations\": {\n                                    \".\": {},\n                                    \"f:kubectl.kubernetes.io/last-applied-configuration\": {}\n                                },\n                                \"f:labels\": {\n                                    \".\": {},\n                                    \"f:kblt.creation.resource\": {}\n                                }\n                            },\n                            \"f:roleRef\": {},\n                            \"f:subjects\": {}\n                        }\n                    }\n                ]\n            },\n            \"subjects\": [\n                {\n                    \"kind\": \"ServiceAccount\",\n                    \"name\": \"kblt-user\",\n                    \"namespace\": \"kubelatte\"\n                },\n                {\n                    \"kind\": \"ServiceAccount\",\n                    \"name\": \"kbl3-user\",\n                    \"namespace\": \"kubelatte\"\n                }\n            ],\n            \"roleRef\": {\n                \"apiGroup\": \"rbac.authorization.k8s.io\",\n                \"kind\": \"ClusterRole\",\n                \"name\": \"synapse-kubelatte-admin-cr\"\n            }\n        },\n        \"oldObject\": null,\n        \"dryRun\": false,\n        \"options\": {\n            \"kind\": \"CreateOptions\",\n            \"apiVersion\": \"meta.k8s.io/v1\",\n            \"fieldManager\": \"kubectl-client-side-apply\"\n        }\n    }"
	yaml.Unmarshal([]byte(data), &object)
	result, details := rego.CheckRule("test/test", map[string]interface{}{}, object)
	assert.Equal(t, false, result)
	assert.Equal(t, 1, len(details))
	for _, detail := range details {
		fmt.Println(detail)
	}
}

func TestOpaManager_RemoveObject(t *testing.T) {
	type fields struct {
		Rules      map[string]rego.PreparedEvalQuery
		senderMode bool
	}
	type args struct {
		message bell.Message
	}
	tests := []struct {
		name     string
		fields   fields
		args     args
		template v1alpha1.Template
	}{
		{
			name: "case_1",
			fields: fields{
				senderMode: true,
			},
			template: v1alpha1.Template{
				ObjectMeta: metav1.ObjectMeta{
					Namespace: "NS",
					Name:      "Name",
				},
			},
		},
		{
			name: "case_2",
			fields: fields{
				senderMode: false,
			},
			template: v1alpha1.Template{
				ObjectMeta: metav1.ObjectMeta{
					Namespace: "NS",
					Name:      "Name",
				},
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			var eventSent bool
			defer func() { eventSent = false }()
			defer func() {
				if tt.fields.senderMode {
					if !eventSent {
						t.Error("message was not sent with enabled senderMode")
					}
					return
				}
				if eventSent {
					t.Error("message was sent with disabled senderMode")
				}
			}()

			bell.Listen(SyncAddNewObjectEvent, func(message bell.Message) { eventSent = true })
			defer bell.Remove(SyncAddNewObjectEvent)

			m := &OpaManager{
				rwMutex:    sync.Mutex{},
				Rules:      map[string]rego.PreparedEvalQuery{tt.template.GetNamespacedName(): {}},
				senderMode: tt.fields.senderMode,
			}

			m.RemoveObject(bell.Message{Value: tt.template})
			if _, ok := m.Rules[tt.template.GetNamespacedName()]; ok {
				t.Errorf("the rule was not removed")
			}
		})
	}
}

func TestOpaManager_GetRegoTemplates(t *testing.T) {
	type fields struct {
		rwMutex    sync.Mutex
		Rules      map[string]rego.PreparedEvalQuery
		senderMode bool
	}
	tests := []struct {
		name   string
		fields fields
		want   map[string]rego.PreparedEvalQuery
	}{
		{
			name:   "ok",
			fields: fields{Rules: map[string]rego.PreparedEvalQuery{"rule": {}}},
			want:   map[string]rego.PreparedEvalQuery{"rule": {}},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			m := &OpaManager{Rules: tt.fields.Rules}
			assert.Equalf(t, tt.want, m.GetRegoTemplates(), "GetRegoTemplates()")
		})
	}
}

func TestOpaManager_AddNewObject(t *testing.T) {
	logs.Logger = zap.NewNop()

	type fields struct {
		Rules      map[string]rego.PreparedEvalQuery
		senderMode bool
	}
	type args struct {
		message bell.Message
	}
	tests := []struct {
		name   string
		fields fields
		args   args
	}{
		{
			name: "case_1",
			fields: fields{
				Rules:      map[string]rego.PreparedEvalQuery{"some_rule": {}},
				senderMode: true,
			},
			args: args{message: bell.Message{
				Value: v1alpha1.Template{
					ObjectMeta: metav1.ObjectMeta{
						Name:      "name",
						Namespace: "Ns",
					},
					Spec: v1alpha1.TemplateSpec{
						Data: "",
					},
				}}},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			var eventSent bool
			defer func() { eventSent = false }()
			defer func() {
				if tt.fields.senderMode {
					if !eventSent {
						t.Error("message was not sent with enabled senderMode")
					}
					return
				}
				if eventSent {
					t.Error("message was sent with disabled senderMode")
				}
			}()

			bell.Listen(SyncAddNewObjectEvent, func(message bell.Message) {
				eventSent = true
			})
			defer bell.Remove(SyncAddNewObjectEvent)

			m := &OpaManager{
				rwMutex:    sync.Mutex{},
				Rules:      tt.fields.Rules,
				senderMode: tt.fields.senderMode,
			}
			m.AddNewObject(tt.args.message)
			time.Sleep(5000 * time.Millisecond)
		})
	}
}
